import asyncio
from typing import List, Set, Tuple, Dict, Any

from crawl4ai import AsyncWebCrawler, LLMExtractionStrategy, CrawlerRunConfig, CacheMode

from utils.data_utils import save_to_csv
from utils.scraper_utils import check_no_results


class BaseCrawler:
    """基础爬虫类，定义通用爬虫行为和接口
    所有特定网站的爬虫都应该继承这个类并实现特定的方法
    """
    
    def __init__(self, config: Dict[str, Any]):
        """初始化爬虫
        
        Args:
            config: 爬虫配置，包含网站特定参数
        """
        self.config = config
        self.browser_config = None
        self.llm_strategy = None
        self.session_id = config.get('session_id', 'default_session')
        self.base_url = config.get('base_url', '')
        self.css_selector = config.get('css_selector', '')
        self.required_keys = config.get('required_keys', [])
        
    async def setup(self):
        """设置爬虫环境，初始化必要的组件
        子类可以重写此方法以提供特定的设置逻辑
        """
        from utils.scraper_utils import get_browser_config, get_llm_strategy
        
        self.browser_config = get_browser_config()
        self.llm_strategy = get_llm_strategy()
    
    async def fetch_page(self, crawler: AsyncWebCrawler, page_number: int, seen_names: Set[str]) -> Tuple[List[dict], bool]:
        """抓取单个页面的数据
        
        Args:
            crawler: 爬虫实例
            page_number: 页码
            seen_names: 已经见过的名称集合，用于去重
            
        Returns:
            Tuple[List[dict], bool]: 处理后的数据列表和是否没有更多结果的标志
        """
        url = f"{self.base_url}?page={page_number}"
        print(f"Loading page {page_number}...")
        
        # 检查是否有"No Results Found"消息
        no_results = await check_no_results(crawler, url, self.session_id)
        if no_results:
            return [], True  # 没有更多结果，发出停止爬取的信号
        
        # 使用提取策略获取页面内容
        result = await crawler.arun(
            url=url,
            config=CrawlerRunConfig(
                cache_mode=CacheMode.BYPASS,  # 不使用缓存数据
                extraction_strategy=self.llm_strategy,  # 数据提取策略
                css_selector=self.css_selector,  # 目标页面上的特定内容
                session_id=self.session_id,  # 爬取的唯一会话ID
            ),
        )
        
        # 处理提取的数据
        return await self.process_page_result(result, seen_names)
    
    async def process_page_result(self, result, seen_names: Set[str]) -> Tuple[List[dict], bool]:
        """处理页面结果
        
        Args:
            result: 爬虫运行结果
            seen_names: 已经见过的名称集合，用于去重
            
        Returns:
            Tuple[List[dict], bool]: 处理后的数据列表和是否没有更多结果的标志
        """
        # 子类应该实现此方法以处理特定网站的数据
        raise NotImplementedError("子类必须实现process_page_result方法")
    
    async def crawl(self) -> List[dict]:
        """执行爬取过程
        
        Returns:
            List[dict]: 爬取到的所有数据
        """
        # 确保爬虫已经设置好
        if not self.browser_config or not self.llm_strategy:
            await self.setup()
        
        # 初始化状态变量
        page_number = 1
        all_data = []
        seen_names = set()
        
        # 开始爬虫上下文
        async with AsyncWebCrawler(config=self.browser_config) as crawler:
            while True:
                # 获取并处理当前页面的数据
                data, no_results_found = await self.fetch_page(
                    crawler,
                    page_number,
                    seen_names,
                )
                
                if no_results_found:
                    print("No more data found. Ending crawl.")
                    break  # 当出现"No Results Found"消息时停止爬取
                
                if not data:
                    print(f"No data extracted from page {page_number}.")
                    break  # 如果没有提取到数据则停止
                
                # 将此页面的数据添加到总列表中
                all_data.extend(data)
                page_number += 1  # 移动到下一页
                
                # 在请求之间暂停以避免速率限制
                await asyncio.sleep(2)  # 根据需要调整睡眠时间
        
        return all_data
    
    async def run(self) -> None:
        """运行爬虫并保存结果"""
        all_data = await self.crawl()
        
        # 保存收集到的数据到CSV文件
        if all_data:
            output_file = self.config.get('output_file', 'output.csv')
            save_to_csv(all_data, output_file)
            print(f"Saved {len(all_data)} items to '{output_file}'.")
        else:
            print("No data were found during the crawl.")
        
        # 显示LLM策略的使用统计信息
        if hasattr(self.llm_strategy, 'show_usage'):
            self.llm_strategy.show_usage()